
public class ContigCollection
{
  //----------------------------------------------------------------FIELDS---------------------------------------------------------------//
  /*
  CONTIG COLLECTION LOCATION FIELDS
    - static location fields
  */
  float x1, y1, x2, y2;//static position-- to keep track of initial position
  Integrator dynX1, dynY1, dynX2, dynY2; //current/moving position
  
  /*
  CONTIG SPACING FIELDS
  */
  float contigSpaceHeight; //this is calculated by partitionVerticalSpace function
  float gapsBetweenContigs; //this is calculated by partitionVerticalSpace function
  float xBorderSize; //size of empty space around edge of ContigCollection
  float yBorderSize;
  float maxContigWidth; //maximum horizontal space consumed by contig 
  float[] y1Positions;
  float[] y2Positions;
  int numOfContigsOnScreen;
  int maxNTOnScreen;
  
  /*
  ABOUT CONTIG COLLECTION FIELDS
  */
  String name;
  
  /*
  FIELDS TO ACCESS AND STORE CONTIGS
  */
  ContigAndGeneReader reader;
  Contig[] contigs;
  HashMap mapNameToPairArray;
  HashMap mapLengthToPairArray;
  
  int uniqueNamesCount;
  int uniqueLengthsCount;
  
  int cryCount;
  int ticCount;
  
  ArrayList cryGenes;
  ArrayList ticGenes;
  
  int[] cryColors;
  int[] ticColors;
 
  class Pair
  {
    int contigNum;
    int geneNum; 
    
    Pair( int contigNum, int geneNum )
    {
      this.contigNum = contigNum;
      this.geneNum = geneNum;
    }
  }
  
  //--------------------------------------------------------------END FIELDS--------------------------------------------------------------//
  //--------------------------------------------------------------CONSTRUCTORS--------------------------------------------------------------//
  /*
  CONSTRUCTOR
    Parameters:
      - filename = name of file in which contig data found
      - name = label to give collection of contigs
      - x1, x2, y1, y2 = positional information about the contig collection
  */
  ContigCollection( String filename, String name, float x1, float y1, float x2, float y2 )
  {
    //position contig collection in space
    this.x1 = x1;
    this.y1= y1;
    this.x2 = y2;
    this.y1 = y1;
    this.y2 = y2;
    
    dynX1 = new Integrator( x1 );
    dynY1 = new Integrator( y1 );
    dynX2 = new Integrator( x2 );
    dynY2 = new Integrator( y2 );
  
    //give collection a name
    this.name = name;
    
    //open and read file
    reader = new ContigAndGeneReader( filename );
    println( reader ); //show contents of file
  
    println( reader.getNumberOfContigs() ); //check for number of contigs
  
    //setup the contig viewing space
    xBorderSize = .01*(x2-x1); //this sets distance between all elements and the edge of the contig collection
    yBorderSize = .01*(y2-y1);
    maxContigWidth = (dynX2.value-dynX1.value) - 2*xBorderSize;
    partitionVerticalSpace( reader.getNumberOfContigs(), .5, borderSize); //based on number of contigs, divide space vertically
    
    //CREATE ARRAY OF CONTIGS 
    contigs = new Contig[ reader.getNumberOfContigs() ];
    for(int i = 0; i < reader.getNumberOfContigs(); i++ )
    {
      contigs[i] = reader.getContig( i );
      contigs[i].target( dynX1.value+xBorderSize, y1Positions[i], dynX2.value-xBorderSize, y2Positions[i] );
    }
    
    //maxNT already set in Contig and gene reader, but now I want to target maxNT of elements on screen
    //numOfContigsOnScreen = 10;
    numOfContigsOnScreen = reader.getNumberOfContigs();
    
    findAndTargetOnScreenMaxNT(); //will reset so onscreen max nt is only thing considered
    
    createHashMaps(); 
    
    assignGeneColors();//based on name
    
    //sortContigs();
    //for(int i = 0; i < reader.getNumberOfContigs(); i++ )
    //{
      //contigs[i].target( dynX1.value+xBorderSize, y1Positions[i], dynX2.value-xBorderSize, y2Positions[i] );
    //}
    
    
  }//end constructor
  //------------------------------------------------------------END CONSTRUCTORS--------------------------------------------------------------// 
  
  //--------------------------------------------------------------SETUP METHODS--------------------------------------------------------------//
  /*
  Method:  partitionVerticalSpace
  Parameters: numberOfVerticalUnits
  Action: divides the vertical space evenly based on 
    - the number of vertical units
    - the ratio between size of the gap between units and vertical unit size
    - the number of gaps at top
    - the number of gaps at bottom
  
    Eq1: availableHeight = totalHeight - 2*borderSize
    Eq2: gapSize/vertUnitSize = ratio
       gapSize = ratio*vertUnitSize
    Eq3: availableHeight = numOfGaps*gapSize + numOfVerticalUnits*vertUnitSize
         availableHeight = numOfGaps*(ratio*vertUnitSize) + numOfVerticalUnits*vertUnitSize
         availableHeight = vertUnitSize( numOfGaps*ratio + numOfVerticalUnits )
       vertUnitSize = availableHeight / ( numOfGaps*ratio + numOfVerticalUnits )
  */
  void partitionVerticalSpace( int numberOfContigs, float gapToContigRatio, float borderSize )
  {
    float availableHeight = (dynY2.value-dynY1.value) - xBorderSize - yBorderSize; //remove the border from top and bottom
    int numOfGaps = numberOfContigs - 1;
  
    contigSpaceHeight = availableHeight / ( numOfGaps*gapToContigRatio + numberOfContigs );
    gapsBetweenContigs = gapToContigRatio * contigSpaceHeight;
  
    println( "contigSpaceHeight = " + contigSpaceHeight + " gapsBetweenContigs " + gapsBetweenContigs );
    
    y1Positions = new float[ numberOfContigs ];
    y2Positions = new float[ numberOfContigs ];
    
    //set position of first element
    y1Positions[0] = dynY1.value + yBorderSize;
    y2Positions[0] = y1Positions[0] + contigSpaceHeight;
    for( int i = 1; i < numberOfContigs; i++ )//then set position of other elements
    {
      y1Positions[i] = y2Positions[i-1] + gapsBetweenContigs; //previous end coordinate + distance between contigs
      y2Positions[i] = y1Positions[i] + contigSpaceHeight; //start + height of contig
    }
  }//end partitionVerticalSpace
  
  void findAndTargetOnScreenMaxNT()
  {
    maxNTOnScreen = 0;
    for(int i = 0; i < numOfContigsOnScreen; i++)
    {
      if( contigs[i].getStop() > maxNTOnScreen )
        maxNTOnScreen = contigs[i].getStop();
    }
    
    //then reset to target new max nt
    for(int i = 0; i < numOfContigsOnScreen; i++)
    {
      contigs[i].targetMaxNT( maxNTOnScreen );
    }
  }
  
  void createHashMaps()
  {
    uniqueNamesCount = 0;
    uniqueLengthsCount = 0;
    
    mapNameToPairArray = new HashMap();
    mapLengthToPairArray = new HashMap();
    
    cryGenes = new ArrayList();
    ticGenes = new ArrayList();

    // fill maps with array lists for all names
    for(int i = 0; i < contigs.length; i++ )//for all contigs
    {
      for(int j = 0; j < contigs[i].getNumberOfGenes() ; j++)//for all genes in the contig
      {
        //get gene
        Gene g = contigs[i].getGene(j);
        String name = g.getToxinName();
        int len = g.getLength();
        
        Pair pair = new Pair( i, j ); //create a pair for that gene
        
        //FIRST CHECK NAME MAP
        //see if this name has previously been encountered, 
        //if so, add the pair to the array list already created
        ArrayList namePairs = (ArrayList) mapNameToPairArray.get( name );
        if( namePairs != null )
        {
          namePairs.add( (Object) pair ); //add this pair to array list
        }
        else //need to create pair array list and add this element to it
        {
          namePairs = new ArrayList();
          namePairs.add( pair );
          
          mapNameToPairArray.put( name, namePairs );
          
          uniqueNamesCount++;
          
          if( name.length() >= 3)
          {
            if( name.substring(0,3).equalsIgnoreCase( "cry" ) )
            {
              cryGenes.add( name );
              cryCount++;
              //contigs[i].assignGeneColor(j, 0x4CC4D4 );
            }
            if( name.substring(0,5).equalsIgnoreCase( "ictic" ) ) 
            {
              ticGenes.add( name );
              ticCount++;
              //contigs[i].assignGeneColor( j, 0x43D4A0 );
            }
          }
        }
        
        //THEN CHECK LENGTH MAP
        ArrayList lengthPairs = (ArrayList) mapLengthToPairArray.get( len );
        if( lengthPairs != null )
        {
          lengthPairs.add( pair ); //add this pair to array list
        }
        else //need to create pair array list and add this element to it
        {
          lengthPairs = new ArrayList();
          lengthPairs.add( pair );
          
          mapLengthToPairArray.put( len, lengthPairs );
          uniqueLengthsCount++;
        }
      }
    }
    
    println( "unique names count = " + uniqueNamesCount + " mapNameToPairArray size = " + mapNameToPairArray.size() );
    println( "unique lengths count = " + uniqueLengthsCount + " mapLengthToPairArray size = " + mapLengthToPairArray.size() );
    println( "cry count = " + cryCount + " ticCount = " + ticCount );
  }//end method
  
  void assignGeneColors()
  {
  
    cryColors = new int[ cryCount ];
    ticColors = new int[ ticCount ];  
    
     // fill maps with array lists for all names
    for(int i = 0; i < contigs.length; i++ )//for all contigs
    {
      for(int j = 0; j < contigs[i].getNumberOfGenes() ; j++)//for all genes in the contig
      {
        //get gene
        Gene g = contigs[i].getGene(j);
        String name = g.getToxinName();
        int len = g.getLength();
          
        if( name.length() >= 3)
        {
           if( name.substring(0,3).equalsIgnoreCase( "cry" ) )
           {
             contigs[i].assignGeneColor(j, 0xFF589EE8);//0xFF5386D4 );//0xFF4CC4D4 );
           }
           if( name.substring(0,5).equalsIgnoreCase( "ictic" ) ) 
           {
             contigs[i].assignGeneColor( j, 0xFF4CE89B );//0xFFD453B4 );//0xFFA053D4);//0xFF43D4A0 );
           }
         }
       }
     }

  }
  
  void colorGenesByGeneName()
  {
    //for all gene instances in gene hash map
      //get gene
      //assign color 
  }
  
  void sortContigs()
  {
    Arrays.sort( contigs );
    //for each contig i = 0 ... n-1
      //for each contig j=1....n-1
        //if contigs are completely identical in content and order
            //swap j with i+1
        //if not identical
          //go through from i+1 to j and find where it belongs based on some measure of degree of similarity-- percent of genes in common plus percent of genes with same count
    
    
    //now contigs that are identical will be next to each other with similar contigs close 
        
    
    
  }
  //-----------------------------------------------------------END SETUP METHODS--------------------------------------------------------------//
  
  //-------------------------------------------------------------DRAW METHODS-----------------------------------------------------------------//
  void draw()
  {
    Gene highlightedGene = null; //store gene that is hovered over so can find other instances
    
    //draw contigs
    for(int i = 0; i < contigs.length; i++)
    {
      contigs[i].draw();
      if( contigs[i].mouseIsOverPrecise() ) //if mouse is over contig, get gene 
      {
        highlightedGene = contigs[i].getGene( mouseX, mouseY ); //get the gene under the mouse
        //if( highlightedGene != null )
          //println( "  inside for loop not null");
      }
    }
    
    if( highlightedGene != null )
    {
        //println( "  outside for loop not null");
          highlightAllInstancesOfGene( highlightedGene );
          highlightedGene.draw(); //redraw
    }
          
    //take care of any moving objects
    if( this.isTargeting() )
    {
      this.update();
    }
  }
  
  void highlightAllInstancesOfGene( Gene g )
  {
    String name = g.getToxinName();
    if( name.equalsIgnoreCase( "na" ) ) //use length hash table to find other gene instances
    {
      int len = g.getLength();
      ArrayList al = (ArrayList) mapLengthToPairArray.get( len );
      if( al == null )
      {
        println( "error- no genes with that length" );
        return;
      }
      for(int i = 0; i < al.size(); i++)
      {
        Pair p = (Pair) al.get(i);
        contigs[p.contigNum].highlightGene( p.geneNum );
      }
    }
    else //use name hash table to find other gene instances
    {
      ArrayList al = (ArrayList) mapNameToPairArray.get(name );
      if( al == null )
      {
        println( "error- no genes with that name" );
        return;
      }
      for(int i = 0; i < al.size(); i++)
      {
        Pair p = (Pair) al.get(i);
        contigs[p.contigNum].highlightGene( p.geneNum );
      }
    }
  }
  //-----------------------------------------------------------END DRAW METHODS---------------------------------------------------------------//
  //------------------------------------------------------------INTERACTION METHODS----------------------------------------------------------//
  void selectionMade( float mx, float my )
  {
    println( "gene selected!");
    Gene targetGene = null;
    //find gene assoc with x and y
    for(int i = 0; i< contigs.length; i++)
    {
      if( contigs[i].mouseIsOverPrecise() )//isOverPrecise(mx, my) )
      {
        println( "is over contig" );
        targetGene = contigs[i].getGene(mouseX, mouseY); //should be mx and my
        if( targetGene == null )
          return;
        println("is over gene");
        float gX1 = targetGene.getActualX1();
        float gX2 = targetGene.getActualX2();
        float geneCenter = (gX1+gX2)/2;
        float distToCenter = (x2+x1)/2 - geneCenter; 
        println( "distance to center = " + distToCenter );

        //contigs[i].target( contigs[i].getX1()+distToCenter, contigs[i].getY1(), contigs[i].getX2()+distToCenter, contigs[i].getY2() );

        ArrayList al = getArrayListForGene( targetGene );
        if( al == null )
        {
          println( "error- no genes with that name or length" );
          return;
        }
        for(int j = 0; j < al.size(); j++)
        {
          Pair p = (Pair) al.get(j);
          Gene g = (Gene) contigs[p.contigNum].getGene( p.geneNum);
          
          //if points different direction, contig should be flipped, then can align
          if( g.pointsRight != targetGene.pointsRight )
          {
            println( "flip " + p.contigNum);
            contigs[p.contigNum].flip();
          }
          
          gX1 = g.getActualX1();
          gX2 = g.getActualX2();
          float gCenter = (gX1+gX2)/2;
          distToCenter = geneCenter - gCenter;
          
          contigs[p.contigNum].target( contigs[p.contigNum].getActualX1()+distToCenter, contigs[p.contigNum].getActualY1(), contigs[p.contigNum].getActualX2()+distToCenter, contigs[p.contigNum].getActualY2() );
        }
        return;
         
      }
     
    }
  }
  //-----------------------------------------------------------END INTERACTION METHODS----------------------------------------------------------
  //-----------------------------------------------------------ANIMATION METHODS----------------------------------------------------------------
  void targetX1(float newX1)
  {
    this.target( newX1, dynY1.target, dynX2.target, dynY2.target );
    //dynX1.target( newX1 );
  }
  
  void targetY1(float newY1)
  {
    this.target( dynX1.target, newY1, dynX2.target, dynY2.target );
    //dynY1.target( newY1 );
  }
  
  void targetX2(float newX2)
  {
    this.target( dynX1.target, dynY1.target, newX2, dynY2.target );
    //dynX2.target( newX2 );
  }
  
  void targetY2(float newY2)
  {
    this.target( dynX1.target, dynY1.target, dynX2.target, newY2 );
    //dynY2.target( newY2 );
  }
  
  void target( float newX1, float newY1, float newX2, float newY2 )
  {
    dynX1.target( newX1 );
    dynX2.target( newX2 );
    dynY1.target( newY1 );
    dynY2.target( newY2 );
  }
  
  boolean isTargeting()
  {
    return dynX1.isTargeting() || dynX2.isTargeting() || dynY1.isTargeting() || dynY2.isTargeting(); //if any one element targeting, keep updating  
  }
  
  void update()
  {
    if( dynX1.isTargeting() )
      dynX1.update();
    if( dynX2.isTargeting() )
      dynX2.update();
    if( dynY1.isTargeting() )
      dynY1.update();
    if( dynY2.isTargeting() )
      dynY2.update();
  }
  //---------------------------------------------------------END ANIMATION METHODS--------------------------------------------------------------
  
  //---------------------------------------------------------------GETTERS----------------------------------------------------------------------
  
  int getNumberOfContigs()
  {
    return reader.getNumberOfContigs();
  }
  
  ArrayList getArrayListForGene( Gene g )
  {
    String name = g.getToxinName();
    if( name.equalsIgnoreCase( "na" ) ) //use length hash table to find other gene instances
    {
      int len = g.getLength();
      return (ArrayList) mapLengthToPairArray.get( len );
    }
    //else return by name
    return (ArrayList) mapNameToPairArray.get(name );
  }
  
  //--------------------------------------------------------------END GETTERS-------------------------------------------------------------------
  
  void sortAndRearrange()
  {
    sortContigs();
    for(int i = 0; i < contigs.length; i++ )
    {
      contigs[i].target( dynX1.value+xBorderSize, y1Positions[i], dynX2.value-xBorderSize, y2Positions[i] );
    }
  }
}

